---
title: "Build a CI Workflow"
slug: "/getting-started/quickstarts/ci"
description: "Build a CI workflow with Dagger"
keywords: ["quickstart", "ci", "dagger", "workflow", "hello-dagger"]
---

import PartialIde from '@partials/_ide.mdx';
import VideoPlayer from '@components/VideoPlayer';

# Build a CI Workflow

CI workflows often start simple, but eventually transform into a labyrinth of artisanal shell scripts and/or unmanageable YAML code. Dagger lets you replace those artisanal scripts and YAML with a modern API and cross-language scripting engine.

:::note
This quickstart will guide you through building a CI workflow for an application using Dagger.
:::

## Requirements

This quickstart will take you approximately 10 minutes to complete. You should be familiar with programming in Go, Python, TypeScript, PHP, or Java.

Before beginning, ensure that:
- you have [installed the Dagger CLI](../../installation.mdx).
- you know [the basics of Dagger](../basics/index.mdx).
- you have [Git](https://git-scm.com/downloads) and a container runtime installed on your system and running. This can be [Docker](https://docs.docker.com/engine/install/), [Podman](../../../reference/container-runtimes/podman.mdx), [nerdctl](../../../reference/container-runtimes/nerdctl.mdx), [Apple Container](../../../reference/container-runtimes/apple-container.mdx), or other Docker-like systems.
- you have a GitHub account (optional, only if configuring Dagger Cloud)

:::tip
<PartialIde />
:::

## Get the example application

The example application is a skeleton Vue framework application that returns a "Hello from Dagger!" welcome page. Create a Github repository from the [hello-dagger-template](https://github.com/dagger/hello-dagger-template) and set it as the current working directory:

In the GitHub UI:

![Create a repo from the hello-dagger-template repository](/img/current_docs/getting-started/quickstarts/ci/template.png)

Clone your new repoistory and set it as the current working directory.

Or with the `gh` CLI:
```shell
gh repo create hello-dagger --template dagger/hello-dagger-template --public --clone
cd hello-dagger
```

## Configure Dagger Cloud (optional)

:::important
This step is optional and will create a [Dagger Cloud](https://dagger.io/cloud) account, which is free of charge for a single user. If you prefer not to sign up for Dagger Cloud, you can skip this section.
:::

Dagger Cloud is an online visualization tool for Dagger workflows. It provides a web interface to visualize each step of your workflow, drill down to detailed logs, understand how long operations took to run, and whether operations were cached.

Create a new Dagger Cloud account by running `dagger login`:

```shell
dagger login
```

The Dagger CLI will invite you to authenticate your device by displaying a link containing a unique key. Click the link in your browser, and verify that you see the same key in the Dagger Cloud Web interface.

```shell
$ dagger login
Browser opened to: https://auth.dagger.cloud/activate?user_code=FCNP-SRLM
Confirmation code: FCNP-SRLM
```

Once you confirm your authentication code, your Dagger CLI will be authenticated and you will get redirected to your newly created Dagger Cloud organization.

After successfully creating your organization, all future Dagger workflows can be inspected in Dagger Cloud.

## Initialize a Dagger module

Bootstrap a new Dagger module in Go, Python, TypeScript, PHP, or Java by running `dagger init` in the application's root directory.

<Tabs groupId="language" queryString="sdk">
<TabItem value="go" label="Go">

```shell
dagger init --sdk=go --name=hello-dagger
```

</TabItem>
<TabItem value="python" label="Python">

```shell
dagger init --sdk=python --name=hello-dagger
```

</TabItem>
<TabItem value="typescript" label="TypeScript">

```shell
dagger init --sdk=typescript --name=hello-dagger
```

</TabItem>
<TabItem value="php" label="PHP">

```shell
dagger init --sdk=php --name=hello-dagger
```

</TabItem>
<TabItem value="java" label="Java">

```shell
dagger init --sdk=java --name=hello-dagger
```

</TabItem>
</Tabs>

This will generate a `dagger.json` [module metadata file](https://docs.dagger.io/reference/dagger.schema.json) and a `.dagger` directory containing some boilerplate Dagger Functions as examples.

To see the generated Dagger Functions, run:

```shell
dagger functions
```

You should see information about two auto-generated Dagger Functions: `container-echo` and `grep-dir`.


## Construct a workflow

Replace the generated Dagger module files as described below.

<Tabs groupId="language" queryString="sdk">
<TabItem value="go" label="Go">

Replace the generated `.dagger/main.go` file with the following code, which adds four Dagger Functions to your Dagger module:

```go Go file=./snippets/go/main.go
```

</TabItem>
<TabItem value="python" label="Python">

Replace the generated `.dagger/src/hello_dagger/main.py` file with the following code, which adds four Dagger Functions to your Dagger module:

```python file=./snippets/python/__init__.py
```

</TabItem>
<TabItem value="typescript" label="TypeScript">

Replace the generated `.dagger/src/index.ts` file with the following code, which adds four Dagger Functions to your Dagger module:

```typescript file=./snippets/typescript/index.ts
```

</TabItem>
<TabItem value="php" label="PHP">

Replace the generated `.dagger/src/HelloDagger.php` file with the following code, which adds four Dagger Functions to your Dagger module:

```php file=./snippets/php/src/HelloDagger.php
```

</TabItem>
<TabItem value="java" label="Java">

Replace the generated `.dagger/src/main/java/io/dagger/modules/hellodagger/HelloDagger.java` file with the following code, which adds four Dagger Functions to your Dagger module:

```java file=./snippets/java/src/main/java/io/dagger/modules/hellodagger/HelloDagger.java
```

</TabItem>
</Tabs>

In this Dagger module, each Dagger Function performs a different operation:

- The `publish` Dagger Function tests, builds and publishes a container image of the application to a registry.
- The `test` Dagger Function runs the application's unit tests and returns the results.
- The `build` Dagger Function performs a multi-stage build and returns a final container image with the production-ready application and an NGINX Web server to host and serve it.
- The `build-env` Dagger Function creates a container with the build environment for the application.

## Run the workflow

Dagger Shell is the fastest way to interact with the Dagger API, allowing access to both types and Dagger Functions using a familiar Bash-like syntax. Type `dagger` to launch Dagger Shell in interactive mode.

```shell
publish
```

This single command runs the application's tests, then builds and publishes it as a container image to the [ttl.sh container registry](https://ttl.sh). Here's what you should see:

<VideoPlayer src="/img/current_docs/getting-started/quickstarts/ci/publish.webm" alt="Publishing the container" />

You can test the published container image by pulling and running it with `docker run`:

<VideoPlayer src="/img/current_docs/getting-started/quickstarts/ci/docker.webm" alt="Running the published container with Docker" />

If you signed up for Dagger Cloud, the output of the previous command would have also included a link to visualize the workflow run on Dagger Cloud. Click the link in your browser to see a complete breakdown of the steps performed. Here's what you should see:

<VideoPlayer src="/img/current_docs/getting-started/quickstarts/ci/trace.webm" alt="Visualizing the workflow in Dagger Cloud" />

## Interact with the build environment

The `build-env` Dagger Function returns a `Container` type representing the application's build environment. One of the most interesting built-in functions of this type is `terminal`, which can be used to [open an interactive terminal session with the running container](../../../introduction/features/observability.mdx).

To try this, chain an additional function call to `terminal` on the returned `Container`:

```shell
build-env | terminal --cmd=bash
```

This command builds the container image and then drops you into an interactive terminal running the `bash` shell. You can now directly execute commands in the running container, as shown below:

<VideoPlayer src="/img/current_docs/getting-started/quickstarts/ci/buildenv-terminal.webm" alt="Interactive terminal in build environment" />

## Run a container as a local service

The `build` Dagger Function returns a `Container` type representing the built container image. Another interesting built-in function to explore here is the `as-service` function, which can be used to start a container as a local service and have any exposed ports forwarded to the host machine. This is similar to Docker Compose, except that you're using code instead of YAML to manage your services.

To try this, use the function chain below:

```shell
build | as-service | up --ports=8080:80
```

By default, Dagger will map each exposed container service port to the same port on the host. Since NGINX operates on port 80, which is often a privileged port on the host, the additional `--ports 8080:80` argument re-maps container port 80 to unprivileged host port 8080.

<VideoPlayer src="/img/current_docs/getting-started/quickstarts/ci/build-service.webm" alt="Final container as service" />

You should now be able to access the application by browsing to `http://localhost:8080` on the Dagger host (replace `localhost` with your Dagger host's network name or IP address if accessing it remotely). You should see a "Hello from Dagger!" welcome page, served by NGINX.

<VideoPlayer src="/img/current_docs/getting-started/quickstarts/ci/curl.webm" alt="Testing the service" />

## Next steps

Congratulations! You've created your first CI workflow with Dagger.

Now you have the tools to successfully take the next step: [adopting Dagger in your project](../../../reference/best-practices/adopting.mdx).

The guide [Use a Blueprint Module](../blueprint/index.mdx) will show you how to use a blueprint module instead of writing code for every single project.

The next guide [Build an AI Agent](../agent/inproject.mdx) will show you how to add agentic capabilities to your newly Daggerized project.

In the meantime, we encourage you to [join our awesome community on Discord](https://discord.gg/dagger-io) to introduce yourself and ask questions. And [starring our GitHub repository](https://github.com/dagger/dagger) is always appreciated!
